"""
This is experimental code to provide writable filesystem capability to
App Engine.  It can be enabled through importing, just with ``import
filesystem``.

This patches all the filesystem-related functions in the system.
Files that aren't in a Python package (under some entry in sys.path)
are handled using the :class:`Files` entity.

To start up, you have to build a basic filesystem, including
``mkdir('/')``.

File permissions are not implemented, and the atime property of files
is not implemented.  Most other things are, but practical usage is
likely to reveal flaws.

This is **very slow**.  Probably too slow to use.  This is the primary
reason why this is experimental.

Files only appear when they are closed.  Files that aren't closed
explicitly are lost, and while in the process of writing a file other
requests will not see the progress.
"""

from google.appengine.ext import db
from google.appengine.ext import gql
import os
import sys
import tempfile
from datetime import datetime
import time
from google.appengine.ext import db
import logging
import itertools
from StringIO import StringIO
'''
        poem = '\
        Programming is fun
        When the work is done
        if you wanna make your work also fun:
                use Python!
        '
        
        f = file('poem.txt', 'w') # open for 'w'riting
        f.write(poem) # write text to file
        f.close() # close the file
        
        f = file('poem.txt')
        # if no mode is specified, 'r'ead mode is assumed by default
        while True:
            line = f.readline()
            if len(line) == 0: # Zero length indicates EOF
                break
            print line,
            # Notice comma to avoid automatic newline added by Python
        f.close() # close the file   
'''
class Files(db.Model):
    basename = db.StringProperty()
    dir = db.StringProperty()
    path = db.StringProperty()
    content = db.BlobProperty()
    mtime_dt = db.DateTimeProperty(auto_now=True)
    ctime_dt = db.DateTimeProperty(auto_now_add=True)
    filetype = db.StringProperty()
    @property
    def is_dir(self):
        return not self.basename

    @classmethod
    def get_file(cls, filename, create=False ,filetype =None):
        query = cls.gql('WHERE path = :1', filename)
        files = query.fetch(1)
        if files:
            return files[0]
        if not create:
            return None
        if filename.startswith("/"):
            if not filetype:
                split = path.rfind('.')  
                ext = path[split + 1:]
            file = cls(
                key_name = filename,
                basename=os.path.basename(filename),
                dir=os.path.dirname(filename),
                path=filename,
                content='',
                mtime_dt=datetime.now(),
                ctime_dt=datetime.now(),
                filetype=ext,
                )
        return file

    @classmethod
    def listdir(cls, dir):
        results =[]
        files = cls.gql('WHERE dir = :1', dir)
        results = [file.basename for file in files]
        return results

    @classmethod
    def mkdir(cls, dir):
        pass

    @classmethod
    def readlink(cls, path):
#        path = os.path.abspath(path)
        obj = cls.get_file(path)
        if obj is None:
            return obj
        return obj.content

    @classmethod
    def remove(cls, path):
#        path = os.path.abspath(path)
        obj = cls.get_file(path)
        if not obj:
            db.delete(obj)

    @classmethod
    def rename(cls, src, dest):
        obj = cls.get_file(src)
        if obj is None:
            return False
        dest_obj = cls.get_file(dest)
        if dest_obj is not None:
            if dest_obj.is_dir:
                return False
            else:
                cls.remove(dest)
                return True
        obj.path = dest
        if obj.is_dir:
            obj.dir = dest
            obj.basename = ''
        else:
            obj.dir = os.path.dirname(dest)
            obj.basename = os.path.basename(dest)
        obj.put()

    @classmethod
    def rmdir(cls, path):
        pass

    @classmethod
    def stat(cls, path):
        obj = cls.get_file(path)
        if obj is None:
            return obj
        return os.stat_result((
            0, # mode
            0, # inode
            0, # device
            1, # hard links
            0, # owner uid
            0, # gid
            len(obj.content or ''), # size
            0, # atime
            obj.mtime, # mtime
            obj.ctime, # ctime
            ))

    @classmethod
    def dump(cls):
        """Returns all the files in a text description
        """
        objs = list(cls.all())
        objs.sort(key=lambda obj: obj.path)
        lines = []
        for obj in objs:
            if obj.is_dir:
                lines.append('Dir:  %s' % obj.path)
            else:
                lines.append('File: %s  (%i bytes)' % (obj.path, len(obj.content or '')))
        return '\n'.join(lines)


def open(oldfilename, mode='r', buffering=0):
    ## FIXME: totally ignore buffering?
    create = mode.startswith('w') or mode.startswith('rw') or mode.startswith('a')
    fileobj = Files.get_file(oldfilename, create=create)
    if fileobj is None:
        return None
    if fileobj.is_dir:
        return None
    return FileWrapper(fileobj, mode=mode)

class FileWrapper(object):

    def __init__(self, fileobj, pos=0, mode='r'):
        self.fileobj = fileobj
        self.pos = pos
        mode = mode.replace('U', '').replace('t', '').replace('b', '')
        assert mode in ('r', 'w', 'rw', 'a'), (
            "Bad mode: %r" % mode)
        self.mode = mode
        if mode == 'a':
            self.pos = len(self.fileobj.content)
        self._closed = False
  
    def read(self, size=-1):
        if size == -1:
            t = self.fileobj.content[self.pos:]
            self.pos = len(self.fileobj.content)
            return t
        else:
            t = self.fileobj.content[self.pos:self.pos+size]
            self.pos = min(len(self.fileobj.content), self.pos+size)
            return t

    def readline(self, size=-1):
        parts = self.fileobj.content[self.pos:].split('\n', 1)
        if len(parts) == 2:
            next = parts[0] + '\n'
        else:
            next = parts[0]
        if size == -1 or size <= len(next):
            self.pos += len(next)
            return next
        else:
            self.pos += size
            return next[:size]

    def readlines(self, size=-1):
        if size == -1:
            rest = self.fileobj.content[self.pos:]
            self.pos = len(self.fileobj.content)
        else:
            rest = self.fileobj.content[self.pos:self.pos+size]
            self.pos += size
        return rest.splitlines(True)

    def write(self, text):
        self.fileobj.content = self.fileobj.content[:self.pos] + text + self.fileobj.content[self.pos+len(text):]
        self.pos += len(text)
        self.fileobj.mtime_dt = datetime.now()
    
    def writelines(self, lines):
        self.write(''.join(lines))

    def seek(self, pos):
        self.assert_state(None)
        self.pos = pos

    def tell(self):
        self.assert_state(None)
        return self.pos

    def close(self):
        self.fileobj.put()
        self._closed = True

    def flush(self):
        self.assert_state(None)
        self.fileobj.put()
